use std::collections::HashMap;
use std::sync::Arc;

use libloading::Symbol;

use common_base::{
    prelude::*,
    serde::{self, Deserialize},
};

use crate::{
    service_engine::service_model::ServiceComponentCreator, support::DylibRepository,
    ServiceComponent,
};

use super::service_plugin_stub::ServicePluginStub;

#[derive(Deserialize, Debug)]
#[serde(crate = "self::serde")]
#[serde(rename_all = "camelCase")]
pub struct ServiceComponentStub {
    comp_creator: String,
    #[serde(rename = "iD")]
    id: String,
    name: String,
    property_map: HashMap<String, String>,
    enabled: bool,
    auto_commit: bool,
    next_service_component_stub: Option<Box<ServiceComponentStub>>,
    service_plugin_stub_list: Vec<ServicePluginStub>,
}

impl ServiceComponentStub {
    // #[async_recursion]
    pub fn load_service_component(
        self,
        dylib_repository: &DylibRepository,
    ) -> Result<Box<dyn ServiceComponent>> {
        let dylib_and_func = self.comp_creator.split("::").collect::<Vec<&str>>();
        if dylib_and_func.len() != 2 || dylib_and_func[0].is_empty() || dylib_and_func[1].is_empty()
        {
            return Err(ErrorCode::DylibLoadFailed(format!(
                "ServiceComponentStub[{}] comp_creator format error, should be `dylib_name::creator_func_name`, but is {}.",
                self.id,
                self.comp_creator
            )));
        }
        let mut service_component = unsafe {
            let _guard = dylib_repository.get_or_create(dylib_and_func[0])?;
            let func: Symbol<ServiceComponentCreator> =
                _guard.value().get(dylib_and_func[1].as_bytes())?;
            *Box::from_raw(func())
        };
        info!("load {} successed", dylib_and_func[1]);

        let base = service_component.get_service_component_base_mut();
        base.id = self.id;
        base.name = self.name;
        base.enabled = self.enabled;
        base.auto_commit = self.auto_commit;
        base.property_map = self.property_map;

        let mut plugins = Vec::with_capacity(self.service_plugin_stub_list.len());
        for plugin_stub in self.service_plugin_stub_list {
            plugins.push(Arc::from(
                plugin_stub.load_service_plugin(dylib_repository)?,
            ));
        }
        base.service_plugin_list = plugins;

        if let Some(next) = self.next_service_component_stub {
            let next = Arc::from(next.load_service_component(dylib_repository)?);
            base.next_service_component = Some(next);
        }

        Ok(service_component)
    }
}
